// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
// SPDX-License-Identifier: MIT
package metrics

import (
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// 测试创建新的配置收集器
func TestNewSquidConfigFilesCollector(t *testing.T) {
	configDir := "/etc/squid"
	collector := NewSquidConfigFilesCollector(configDir)

	assert.NotNil(t, collector, "收集器不应为空")
	assert.Equal(t, configDir, collector.GetConfigDir(), "配置目录应匹配")
	assert.NotNil(t, collector.filesCount, "文件计数指标不应为空")
	assert.NotNil(t, collector.totalSize, "总大小指标不应为空")
	assert.NotNil(t, collector.lastScanTime, "最后扫描时间指标不应为空")
	assert.NotNil(t, collector.scanSuccess, "扫描成功指标不应为空")
	assert.NotNil(t, collector.fileInfo, "文件信息描述不应为空")
	assert.NotNil(t, collector.fileTypesCount, "文件类型计数描述不应为空")
	assert.NotNil(t, collector.recentlyChanged, "最近修改文件描述不应为空")
}

// 测试获取配置目录
func TestSquidConfigFilesCollector_GetConfigDir(t *testing.T) {
	configDir := "/tmp/squid_config"
	collector := NewSquidConfigFilesCollector(configDir)

	assert.Equal(t, configDir, collector.GetConfigDir(), "配置目录应匹配")
}

// 测试描述方法
func TestSquidConfigFilesCollector_Describe(t *testing.T) {
	collector := NewSquidConfigFilesCollector("/tmp")

	ch := make(chan *prometheus.Desc, 10)
	collector.Describe(ch)

	// 收集所有描述
	var descs []*prometheus.Desc
	for i := 0; i < 7; i++ {
		select {
		case desc := <-ch:
			descs = append(descs, desc)
		case <-time.After(100 * time.Millisecond):
			break
		}
	}

	assert.GreaterOrEqual(t, len(descs), 7, "应至少描述7个指标")
}

// 测试扫描空目录
func TestSquidConfigFilesCollector_ScanEmptyDirectory(t *testing.T) {
	// 创建临时目录
	tmpDir, err := os.MkdirTemp("", "squid_config_test_empty")
	require.NoError(t, err)
	defer os.RemoveAll(tmpDir)

	collector := NewSquidConfigFilesCollector(tmpDir)

	files, err := collector.scanConfigDirectory()
	require.NoError(t, err)
	assert.Empty(t, files, "空目录应返回空文件列表")
}

// 测试扫描包含文件的目录
func TestSquidConfigFilesCollector_ScanDirectoryWithFiles(t *testing.T) {
	// 创建临时目录
	tmpDir, err := os.MkdirTemp("", "squid_config_test_files")
	require.NoError(t, err)
	defer os.RemoveAll(tmpDir)

	// 创建测试文件
	testFiles := []struct {
		name      string
		content   string
		extension string
	}{
		{"squid.conf", "http_port 3128\ncache_dir ufs /var/spool/squid 100 16 256", "conf"},
		{"acl.conf", "acl localnet src 192.168.0.0/16", "conf"},
		{"mime.conf", "text/html html htm", "conf"},
		{"readme.txt", "Squid configuration files", "txt"},
		{"backup.conf.bak", "backup file", "bak"},
	}

	for _, tf := range testFiles {
		filePath := filepath.Join(tmpDir, tf.name)
		err := os.WriteFile(filePath, []byte(tf.content), 0644)
		require.NoError(t, err)
	}

	collector := NewSquidConfigFilesCollector(tmpDir)

	files, err := collector.scanConfigDirectory()
	require.NoError(t, err)
	assert.Len(t, files, len(testFiles), "应找到所有测试文件")

	// 验证文件信息
	for _, file := range files {
		assert.NotEmpty(t, file.Name, "文件名不应为空")
		assert.NotEmpty(t, file.Path, "文件路径不应为空")
		assert.False(t, file.IsDirectory, "文件不应是目录")
		assert.True(t, file.IsRegular, "文件应是常规文件")
		assert.NotZero(t, file.Size, "文件大小不应为零")
		assert.NotZero(t, file.ModTime, "修改时间不应为零")
		assert.NotEmpty(t, file.Permissions, "权限字符串不应为空")
	}
}

// 测试扫描包含子目录的目录
func TestSquidConfigFilesCollector_ScanDirectoryWithSubdirectories(t *testing.T) {
	// 创建临时目录
	tmpDir, err := os.MkdirTemp("", "squid_config_test_subdirs")
	require.NoError(t, err)
	defer os.RemoveAll(tmpDir)

	// 创建子目录
	subDir := filepath.Join(tmpDir, "conf.d")
	err = os.Mkdir(subDir, 0755)
	require.NoError(t, err)

	// 在子目录中创建文件
	filePath := filepath.Join(subDir, "extra.conf")
	err = os.WriteFile(filePath, []byte("acl extra src 10.0.0.0/8"), 0644)
	require.NoError(t, err)

	collector := NewSquidConfigFilesCollector(tmpDir)

	files, err := collector.scanConfigDirectory()
	require.NoError(t, err)
	assert.Len(t, files, 2, "应找到子目录和文件") // 子目录本身和其中的文件

	// 验证子目录信息
	var foundSubDir bool
	var foundFile bool
	for _, file := range files {
		if file.IsDirectory {
			foundSubDir = true
			assert.Equal(t, "conf.d", file.Name, "子目录名应匹配")
		} else {
			foundFile = true
			assert.Equal(t, "extra.conf", file.Name, "文件名应匹配")
		}
	}

	assert.True(t, foundSubDir, "应找到子目录")
	assert.True(t, foundFile, "应找到文件")
}

// 测试扫描不存在的目录
// func TestSquidConfigFilesCollector_ScanNonExistentDirectory(t *testing.T) {
// 	nonExistentDir := "/this/path/does/not/exist/12345"
// 	collector := NewSquidConfigFilesCollector(nonExistentDir)

// 	files, err := collector.scanConfigDirectory()
// 	assert.NoError(t, err, "扫描不存在的目录不应返回错误")
// 	assert.Empty(t, files, "不存在的目录应返回空文件列表")
// }

// 测试文件扩展名提取
func TestSquidConfigFilesCollector_FileExtensionExtraction(t *testing.T) {
	// 创建临时目录
	tmpDir, err := os.MkdirTemp("", "squid_config_test_extensions")
	require.NoError(t, err)
	defer os.RemoveAll(tmpDir)

	testCases := []struct {
		filename  string
		extension string
	}{
		{"squid.conf", "conf"},
		{"acl.txt", "txt"},
		{"mime", ""},               // 无扩展名
		{"backup.conf.bak", "bak"}, // 多个点，取最后一个
		{".hidden", "hidden"},      // 隐藏文件
	}

	for _, tc := range testCases {
		filePath := filepath.Join(tmpDir, tc.filename)
		err := os.WriteFile(filePath, []byte("test content"), 0644)
		require.NoError(t, err)
	}

	collector := NewSquidConfigFilesCollector(tmpDir)

	files, err := collector.scanConfigDirectory()
	require.NoError(t, err)

	// 创建扩展名映射以便验证
	extensionMap := make(map[string]string)
	for _, file := range files {
		extensionMap[file.Name] = file.Extension
	}

	for _, tc := range testCases {
		actualExtension, exists := extensionMap[tc.filename]
		assert.True(t, exists, "应找到文件: %s", tc.filename)
		assert.Equal(t, tc.extension, actualExtension, "文件 %s 的扩展名应匹配", tc.filename)
	}
}

// 测试收集方法 - 成功情况
func TestSquidConfigFilesCollector_CollectSuccess(t *testing.T) {
	// 创建临时目录
	tmpDir, err := os.MkdirTemp("", "squid_config_test_collect")
	require.NoError(t, err)
	defer os.RemoveAll(tmpDir)

	// 创建测试文件
	testFiles := []struct {
		name    string
		content string
		size    int
	}{
		{"config1.conf", "content1", len("content1")},
		{"config2.conf", "content2", len("content2")},
		{"readme.txt", "readme content", len("readme content")},
	}

	totalSize := 0
	for _, tf := range testFiles {
		filePath := filepath.Join(tmpDir, tf.name)
		err := os.WriteFile(filePath, []byte(tf.content), 0644)
		require.NoError(t, err)
		totalSize += tf.size
	}

	collector := NewSquidConfigFilesCollector(tmpDir)

	ch := make(chan prometheus.Metric, 100)
	collector.Collect(ch)

	// 收集所有指标
	var metrics []prometheus.Metric
	for i := 0; i < 100; i++ {
		select {
		case metric := <-ch:
			metrics = append(metrics, metric)
		case <-time.After(100 * time.Millisecond):
			break
		}
	}

	// 验证收集到了指标
	assert.NotEmpty(t, metrics, "应收集到指标")

	// 验证基础指标
	var filesCountMetric, totalSizeMetric, scanSuccessMetric prometheus.Metric
	for _, metric := range metrics {
		desc := metric.Desc()
		descString := desc.String()

		if contains(descString, "squid_config_files_count") {
			filesCountMetric = metric
		} else if contains(descString, "squid_config_files_total_size_bytes") {
			totalSizeMetric = metric
		} else if contains(descString, "squid_config_scan_success") {
			scanSuccessMetric = metric
		}
	}

	assert.NotNil(t, filesCountMetric, "应收集到文件计数指标")
	assert.NotNil(t, totalSizeMetric, "应收集到总大小指标")
	assert.NotNil(t, scanSuccessMetric, "应收集到扫描成功指标")
}

// 测试最近修改的文件检测
func TestSquidConfigFilesCollector_RecentlyChangedFiles(t *testing.T) {
	// 创建临时目录
	tmpDir, err := os.MkdirTemp("", "squid_config_test_recent")
	require.NoError(t, err)
	defer os.RemoveAll(tmpDir)

	// 创建旧文件（修改时间超过1小时）
	oldFile := filepath.Join(tmpDir, "old.conf")
	err = os.WriteFile(oldFile, []byte("old content"), 0644)
	require.NoError(t, err)

	// 修改文件时间为1小时前
	oldTime := time.Now().Add(-2 * time.Hour)
	err = os.Chtimes(oldFile, oldTime, oldTime)
	require.NoError(t, err)

	// 创建新文件（修改时间在1小时内）
	newFile := filepath.Join(tmpDir, "new.conf")
	err = os.WriteFile(newFile, []byte("new content"), 0644)
	require.NoError(t, err)

	collector := NewSquidConfigFilesCollector(tmpDir)

	ch := make(chan prometheus.Metric, 50)
	collector.Collect(ch)

	// 收集所有指标
	var metrics []prometheus.Metric
	for i := 0; i < 50; i++ {
		select {
		case metric := <-ch:
			metrics = append(metrics, metric)
		case <-time.After(100 * time.Millisecond):
			break
		}
	}

	// 验证最近修改文件数量指标
	var recentlyChangedMetric prometheus.Metric
	for _, metric := range metrics {
		desc := metric.Desc()
		if contains(desc.String(), "squid_config_files_recently_changed") {
			recentlyChangedMetric = metric
			break
		}
	}

	assert.NotNil(t, recentlyChangedMetric, "应收集到最近修改文件指标")
}

// 测试文件类型统计
func TestSquidConfigFilesCollector_FileTypesStatistics(t *testing.T) {
	// 创建临时目录
	tmpDir, err := os.MkdirTemp("", "squid_config_test_types")
	require.NoError(t, err)
	defer os.RemoveAll(tmpDir)

	// 创建不同扩展名的文件
	fileTypes := map[string]int{
		"conf": 3,
		"txt":  2,
		"bak":  1,
	}

	for ext, count := range fileTypes {
		for i := 0; i < count; i++ {
			filename := filepath.Join(tmpDir, "file"+string(rune('a'+i))+"."+ext)
			err := os.WriteFile(filename, []byte("content"), 0644)
			require.NoError(t, err)
		}
	}

	collector := NewSquidConfigFilesCollector(tmpDir)

	ch := make(chan prometheus.Metric, 50)
	collector.Collect(ch)

	// 收集所有指标
	var metrics []prometheus.Metric
	for i := 0; i < 50; i++ {
		select {
		case metric := <-ch:
			metrics = append(metrics, metric)
		case <-time.After(100 * time.Millisecond):
			break
		}
	}

	// 验证文件类型统计
	extensionCounts := make(map[string]int)
	for _, metric := range metrics {
		desc := metric.Desc()
		if contains(desc.String(), "squid_config_file_types_count") {
			// 这里我们无法直接获取标签值，但可以确认指标被创建了
			extensionCounts["found"]++ // 标记找到了文件类型指标
		}
	}

	assert.Greater(t, extensionCounts["found"], 0, "应找到文件类型统计指标")
}

// 测试ConfigFileInfo结构体
func TestConfigFileInfo_Structure(t *testing.T) {
	// 创建临时文件
	tmpFile, err := os.CreateTemp("", "test_file_info")
	require.NoError(t, err)
	defer os.Remove(tmpFile.Name())

	content := "test content for file info"
	_, err = tmpFile.WriteString(content)
	require.NoError(t, err)
	tmpFile.Close()

	// 获取文件信息
	fileInfo, err := os.Stat(tmpFile.Name())
	require.NoError(t, err)

	// 创建ConfigFileInfo
	configFileInfo := ConfigFileInfo{
		Name:        fileInfo.Name(),
		Path:        tmpFile.Name(),
		Size:        fileInfo.Size(),
		ModTime:     fileInfo.ModTime(),
		IsDirectory: fileInfo.IsDir(),
		IsRegular:   fileInfo.Mode().IsRegular(),
		Permissions: fileInfo.Mode().String(),
		Extension:   "tmp",
	}

	// 验证字段
	assert.Equal(t, fileInfo.Name(), configFileInfo.Name, "文件名应匹配")
	assert.Equal(t, tmpFile.Name(), configFileInfo.Path, "文件路径应匹配")
	assert.Equal(t, fileInfo.Size(), configFileInfo.Size, "文件大小应匹配")
	assert.Equal(t, fileInfo.ModTime(), configFileInfo.ModTime, "修改时间应匹配")
	assert.False(t, configFileInfo.IsDirectory, "不应是目录")
	assert.True(t, configFileInfo.IsRegular, "应是常规文件")
	assert.NotEmpty(t, configFileInfo.Permissions, "权限字符串不应为空")
	assert.Equal(t, "tmp", configFileInfo.Extension, "扩展名应匹配")
}

// 检查字符串是否包含子串
func contains(s, substr string) bool {
	return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && (s[0:len(substr)] == substr || contains(s[1:], substr)))
}
